package org.hyh.autoconfigure.core.interceptor;

import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Properties;

/**
 * @author :yihui.He
 * @description :  拦截{@link StatementHandler#query(Statement, ResultHandler)}方法，从而得到将要执行的sql.
 * @date :2021/2/18
 */
/**
 *    mybatis 的 拦截器介绍：
 *     interceptorChain的执行顺序如考如下：
 *      {@link org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)}
 *       //在openSession()创建sqlSession 时，会创建executor时，执行interceptorChain#pluginAll()
 *      {@link org.apache.ibatis.session.Configuration#newStatementHandler(org.apache.ibatis.executor.Executor, org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
 *       //在执行查询时，会创建satementHandler，执行interceptorChain#pluginAll()
 *      {@link org.apache.ibatis.session.Configuration#newParameterHandler(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.mapping.BoundSql)}
 *      // 在执行查询时，会创建satementHandler时内部创建parameterHandler，执行interceptorChain#pluginAll()
 *      {@link org.apache.ibatis.session.Configuration#newResultSetHandler(org.apache.ibatis.executor.Executor, org.apache.ibatis.mapping.MappedStatement, org.apache.ibatis.session.RowBounds, org.apache.ibatis.executor.parameter.ParameterHandler, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.mapping.BoundSql)}
 *      //在执行查询时，会创建satementHandler时内部创建resultHandler，执行interceptorChain#pluginAll()
 *
 *      总结： mybatis 拦截器可以拦截的：  executor 、 statementHandler、 ParameterHandler、ResultSetHandler 这个四个对象 。
 */

/**
 *  拦截器， delete 、 insert 最后都是走update() ，所以这里只是拦截update
 *
 */
@Intercepts({
    @Signature(type = StatementHandler.class,method = "query",args = {Statement.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class,method = "update",args = {Statement.class}),
    @Signature(type = StatementHandler.class,method = "queryCursor",args = {Statement.class})
})
@Slf4j
public class SqlPrintInterceptor implements Interceptor {

    //  用于正则匹配 ，换行符。
    private static final String NEW_LINE_SYSMBOL = "\\r\\n|\\r|\\n|\\n\\r";

    /**
     *  {@link org.apache.ibatis.mapping.StatementType}中
     *    STATEMENT： 静态sql ,直接使用
     *    PREPARED：预编译sql
     *    CALLABLE: 存储过程
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler handler = (StatementHandler)invocation.getTarget();

        Method method = invocation.getMethod();
        Object[] args = invocation.getArgs();
        Statement statement = (Statement)args[0];

        String sql=null;
        // PREPARED、CALLABLE 中使用
        if (statement instanceof PreparedStatement) {
            PreparedStatement preparedStatement = (PreparedStatement)statement;
            // 判断是否是mysql,
            if (preparedStatement.isWrapperFor(com.mysql.jdbc.PreparedStatement.class)) {
                com.mysql.jdbc.PreparedStatement unwrap =
                    preparedStatement.unwrap(com.mysql.jdbc.PreparedStatement.class);
                 sql = unwrap.asSql();
            }
        }else {
            // 说明是static sql  直接获取sql
             sql = handler.getBoundSql().getSql();
        }

        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        Object result = method.invoke(handler, args);
        stopWatch.stop();
        long mills = stopWatch.getTotalTimeMillis();
        System.err.println("\n耗时:"+mills+"ms execute sql:"+formatSql(sql));
        return  result;
    }

    /**
     * {@link Plugin#wrap(Object, Interceptor)}  方法会解析对应的拦截器上的@Intercepts 方法，将其封装成Map<Class<?>, Set<Method>> signatureMap
     *  然后返回 JDK动态代理对象，当执行invoke方法时，如果对应的method 在signatureMap 中就执行对应的拦截器的interceptor()方法。
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return  target;


    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     *  格式化sql, 替换掉 \n 换行符。
     * @param sql
     * @return
     */
    private String formatSql(String sql) throws JSQLParserException {
        if (StringUtils.isBlank(sql)) {
            return "";
        }
        net.sf.jsqlparser.statement.Statement statement = CCJSqlParserUtil.parse(sql);
        return  statement.toString();

    }
}
